﻿using Common;
using FrontEnd.Util;
using Linguist.Acoustic.Tiedstate;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Util;
using Util.Props;

namespace Api
{
    /// <summary>
    ///  Helps to tweak configuration without touching XML-file directly.
    /// </summary>
    public class Context
    {
        private ConfigurationManager configurationManager;

        /**
        /// Constructs builder that uses default XML configuration.
         *
        /// @throws MalformedURLException if failed to load configuration file
         */
        public Context(Configuration config)
            : this(/*"default.config.xml"*/"digits.xml", config)
        {

        }

        /// <summary>
        /// Constructs builder using user-supplied XML configuration.
        /// throws MalformedURLException if failed to load configuration file
        /// throws IOException           if failed to load configuration file
        /// </summary>
        /// <param name="path">path to XML-resource with configuration</param>
        /// <param name="config">the same instance of {@link Configuration}</param>
        public Context(String path, Configuration config)
        {
            configurationManager = new ConfigurationManager(path);

            setAcousticModel(config.getAcousticModelPath());
            setDictionary(config.getDictionaryPath());

            if (null != config.getGrammarPath() && config.getUseGrammar())
                setGrammar(config.getGrammarPath(), config.getGrammarName());
            if (!String.IsNullOrEmpty(config.LanguageModelPath) && !config.getUseGrammar())
                setLanguageModel(config.LanguageModelPath);

            //setSampleRate(config.SampleRate);

            // Force ConfigurationManager to build the whole graph
            // in order to enable instance lookup by class.

  
            configurationManager.lookup("recognizer");
        }

        /**
        /// Sets acoustic model location.
         *
        /// It also reads feat.params which should be located at the root of
        /// acoustic model and sets corresponding parameters of
        /// {@link MelFrequencyFilterBank2} instance.
         *
        /// @param  path path to directory with acoustic model files
         *
        /// @throws IOException if failed to read feat.params
         */
        public void setAcousticModel(String path)
        {
            // digits.xml
            setLocalProperty("sphinx3Loader", "location", path);
            setLocalProperty("dictionary", "fillerPath", path + "/noisedict");

            // default.config.xml
            //setLocalProperty("acousticModelLoader", "location", path);
            //setLocalProperty("dictionary", "fillerPath", path + "/noisedict");
        }

        /**
        /// Sets dictionary.
         *
        /// @param path path to directory with dictionary files
         */
        public void setDictionary(String path)
        {
            setLocalProperty("dictionary", "dictionaryPath", path);
        }

        /**
        /// Sets sampleRate.
         *
        /// @param sampleRate sample rate of the input stream.
         */
        public void setSampleRate(int sampleRate)
        {
            setLocalProperty("StreamDataSource", "sampleRate", sampleRate.ToString());
        }

        /**
        /// Sets path to the grammar files.
         *
        /// Enables static grammar and disables probabilistic language model.
        /// JSGF and GrXML formats are supported.
         *
        /// @param path path to the grammar files
        /// @param name name of the main grammar to use
        /// @see        Context#setLanguageModel(String)
         */
        public void setGrammar(String path, String name)
        {
            // TODO: use a single param of type File, cache directory part
            if (name.EndsWith(".grxml"))
            {
                setLocalProperty("grXmlGrammar", "grammarLocation", path + name);
                setLocalProperty("flatLinguist", "grammar", "grXmlGrammar");
            }
            else
            {
                setLocalProperty("jsgfGrammar", "grammarLocation", path);
                setLocalProperty("jsgfGrammar", "grammarName", name);
                setLocalProperty("flatLinguist", "grammar", "jsgfGrammar");
            }
            setLocalProperty("decoder", "searchManager", "simpleSearchManager");
        }

        /**
        /// Sets path to the language model.
         *
        /// Enables probabilistic language model and disables static grammar.
        /// Currently it supports ".lm" and ".dmp" file formats.
         *
        /// @param  path path to the language model file
        /// @see         Context#setGrammar(String)
         *
        /// @throws IllegalArgumentException if path ends with unsupported extension
         */
        public void setLanguageModel(String path)
        {
            if (path.EndsWith(".lm"))
            {
                setLocalProperty("simpleNGramModel", "location", path);
                setLocalProperty(
                    "lexTreeLinguist", "languageModel", "simpleNGramModel");
            }
            else if (path.EndsWith(".dmp"))
            {
                setLocalProperty("largeTrigramModel", "location", path);
                setLocalProperty(
                    "lexTreeLinguist", "languageModel", "largeTrigramModel");
            }
            else
            {
                throw new ArgumentException("Unknown format extension: " + path);
            }
            setLocalProperty("decoder", "searchManager", "wordPruningSearchManager");
        }


        public void setSpeechSource(FileStream stream, TimeFrame timeFrame)
        {
            
            ((StreamDataSource)getInstance(typeof(StreamDataSource))).setInputStream(stream, timeFrame);
            setLocalProperty("threadedScorer", "frontend", "mfcFrontEnd");
            //setLocalProperty("threadedScorer", "frontend", "liveFrontEnd");
        }

        public void setSpeechSource(String stream)
        {
            ((AudioFileDataSource)getInstance(typeof(AudioFileDataSource))).setAudioFile(stream);
            //((StreamDataSource)getInstance(typeof(StreamDataSource))).setInputStream(stream, timeFrame);
            setLocalProperty("threadedScorer", "frontend", "mfcFrontEnd");
            //setLocalProperty("threadedScorer", "frontend", "liveFrontEnd");
        }

        /**
        /// Sets byte stream as the speech source.
         *
        /// @param  stream stream
        /// @see           Context#useMicrophone()
         */
        public void setSpeechSource(FileStream stream)
        {
            // TODO: setup stream sample rate and other parameters
            ((StreamDataSource)getInstance(typeof(StreamDataSource))).setInputStream(stream);
            setLocalProperty("threadedScorer", "frontend", "liveFrontEnd");
        }

        /**
        /// Sets batch frontend to process whole file.
         *
        /// @see    Context#setSpeechSource(URL)
         */
        public void processBatch()
        {
            setLocalProperty("threadedScorer", "frontend", "batchFrontEnd");
        }

        /**
        /// Sets property within a "component" tag in configuration.
         *
        /// Use this method to alter "value" property of a "property" tag inside a
        /// "component" tag of the XML configuration.
         *
        /// @param  name  property name
        /// @param  value property value
        /// @see          Context#setGlobalProperty(String, Object)
         */
        public void setLocalProperty(String name, String prop, Object value)
        {
            setProperty(configurationManager, name, prop, value.ToString());
        }

        public void setProperty(ConfigurationManager cm, String componentName, String propName, String propValue)
        {

            // now set the property
            PropertySheet ps = cm.getPropertySheet(componentName);
            if (ps == null)
                throw new SystemException("Component '" + propName + "' is not registered to this system configuration '");

            // set the value to null if the string content is 'null
            if (propValue.Equals("null"))
                propValue = null;

            switch (ps.getType(propName))
            {
                case PropertyType.BOOLEAN:
                    ps.setBoolean(propName, Convert.ToBoolean(propValue));
                    break;
                case PropertyType.DOUBLE:
                    ps.setDouble(propName, Convert.ToDouble(propValue));
                    break;
                case PropertyType.INT:
                    ps.setInt(propName, Convert.ToInt32(propValue));
                    break;
                case PropertyType.STRING:
                    ps.setString(propName, propValue);
                    break;
                case PropertyType.COMPONENT:
                    ps.setComponent(propName, propValue, null);
                    break;
                case PropertyType.COMPONENT_LIST:
                    List<String> compNames = new List<String>();
                    foreach (String component in propValue.Split(new String[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        compNames.Add(component.Trim());
                    }

                    ps.setComponentList(propName, compNames, null);
                    break;
                default:
                    throw new SystemException("unknown property-type");
            }
        }


        /**
        /// Sets property of a top-level "property" tag.
         *
        /// Use this method to alter "value" property of a "property" tag whose
        /// parent is the root tag "config" of the XML configuration.
         *
        /// @param  name  property name
        /// @param  value property value
        /// @see          Context#setLocalProperty(String, Object)
         */
        public void setGlobalProperty(String name, Object value)
        {
            configurationManager.setGlobalProperty(name, value.ToString());
        }

        /**
        /// Returns instance of the XML configuration by its class.
         *
        /// @param  clazz class to look up
        /// @return       instance of the specified class or null
         */
        public IConfigurable getInstance(Type clazz)
        {
            return configurationManager.lookup(clazz.Name);
        }

        /**
        /// Returns the Loader object used for loading the acoustic model.
        /// 
        /// @return the loader  object
         */
        public ILoader getLoader()
        {
            return (ILoader)configurationManager.lookup("acousticModelLoader");
        }
    }
}
